package peersim.core;

import peersim.config.Configuration;
import java.util.Comparator;
import java.util.Arrays;

/**
 * This class forms the basic framework of all simulations.
 * This is a static singleton which is based on the assumption that we
 * will simulate only one overlay network at a time.
 * This allows us to reduce memory usage in many cases by allowing all the
 * components to directly reach the fields of this class without having to store
 * a reference.
 * <p>
 * The network is a set of nodes implemented via an array list for the
 * sake of efficiency.
 * Each node has an array of protocols. The protocols within a node can
 * interact directly as defined by their implementation, and can be imagined as
 * processes running in a common local environment (i.e. the node).
 * This class is called a "network" because, although it is only a set of nodes,
 * in most simulations there is at least one {@link Linkable} protocol that
 * defines connections between nodes. In fact, such a {@link Linkable} protocol
 * layer can be accessed through a {@link peersim.graph.Graph} view
 * using {@link OverlayGraph}.
 */
public class Network {

// ========================= fields =================================
// ==================================================================
    /**
     * This config property defines the node class to be used. If not
     * set, then {@link GeneralNode} will be used.
     * @config
     */
    private static final String PAR_NODE = "network.node";
    /**
     * This config property defines the initial capacity of the overlay network.
     * If not set then the value of {@value #PAR_SIZE} will be used.
     * The main purpose of this parameter is that it allows for optimization.
     * In the case of scenarios when the network needs to grow, setting this to
     * the maximal expected size of the network avoids reallocation of memory
     * during the growth of the network.
     * @see #getCapacity
     * @config
     */
    private static final String PAR_MAXSIZE = "network.initialCapacity";
    /**
     * This config property defines the initial size of the overlay network.
     * This property is required.
     * @config
     */
    private static final String PAR_SIZE = "network.size";
    /**
     * The node array. This is not a private array which is not nice but
     * efficiency has the highest priority here. The main purpose is to allow
     * the package quick reading of the contents in a maximally flexible way.
     * Nevertheless, methods of this class should be used instead of the array
     * when modifying the contents.
     * Because this array is not private,
     * it is necessary to know that the actual node set is only the first
     * {@link #size()} items of the array.
     */
    static Node[] node = null;
    /**
     * Actual size of the network.
     */
    private static int len;
    /**
     * The prototype node which is used to populate the simulation via cloning.
     * After all the nodes have been cloned, {@link Control} components can be
     * applied to perform any further initialization.
     */
    public static Node prototype = null;

// ====================== initialization ===========================
// =================================================================
    /**
     * Reads configuration parameters, constructs the prototype node, and
     * populates the network by cloning the prototype.
     *
     * <p>
     * 读取配置参数，构造原型节点，并通过克隆来构造网络中的其它节点。
     */
    public static void reset() {

        if (prototype != null) {
            // not first experiment
            while (len > 0) {
                remove(); // this is to call onKill on all nodes
            }
            prototype = null;
            node = null;
        }

        len = Configuration.getInt(PAR_SIZE);
        int maxlen = Configuration.getInt(PAR_MAXSIZE, len);
        if (maxlen < len) {
            throw new IllegalArgumentException(
                    PAR_MAXSIZE + " is less than " + PAR_SIZE);
        }// 初始大小必须小于最大的数目 

        node = new Node[maxlen];

        // creating prototype node
        Node tmp = null;
        if (!Configuration.contains(PAR_NODE)) {
            System.err.println(
                    "Network: no node defined, using GeneralNode");
            tmp = new GeneralNode("");
        } else {
            tmp = (Node) Configuration.getInstance(PAR_NODE);
        }
        prototype = tmp;
        prototype.setIndex(-1);

        // cloning the nodes
        if (len > 0) {
            for (int i = 0; i < len; ++i) {
                node[i] = (Node) prototype.clone();
                node[i].setIndex(i);
            }
        }
    }

    /** Disable instance construction */
    private Network() {
    }

// =============== public methods ===================================
// ==================================================================
    /** Number of nodes currently in the network */
    public static int size() {
        return len;
    }

// ------------------------------------------------------------------
    /**
     * Sets the capacity of the internal array storing the nodes.
     * The nodes will remain the same in the same order.
     * If the new capacity is less than the
     * old size of the node list, than the end of the list is cut. The nodes that
     * get removed via this cutting are removed through {@link #remove()}.
     */
    public static void setCapacity(int newSize) {

        if (node == null || newSize != node.length) {
            for (int i = newSize; i < len; ++i) {
                remove();
            }
            Node[] newnodes = new Node[newSize];
            final int l = Math.min(node.length, newSize);
            System.arraycopy(node, 0, newnodes, 0, l);
            node = newnodes;
            if (len > newSize) {
                len = newSize;
            }
        }
    }

// ------------------------------------------------------------------
    /**
     * Returns the maximal number of nodes that can be stored without reallocating
     * the underlying array to increase capacity.
     */
    public static int getCapacity() {
        return node.length;
    }

// ------------------------------------------------------------------
    /**
     * The node will be appended to the end of the list. If necessary, the
     * capacity of the internal array is increased.
     */
    public static void add(Node n) {

        if (len == node.length) {
            setCapacity(3 * node.length / 2 + 1);
        }
        node[len] = n;
        n.setIndex(len);
        len++;
    }

// ------------------------------------------------------------------
    /**
     * Returns node with the given index. Note that the same node will normally
     * have a different index in different times.
     * This can be used as a random access iterator.
     * This method does not perform range checks to increase efficiency.
     * The maximal valid index is {@link #size()}.
     */
    public static Node get(int index) {

        return node[index];
    }

// ------------------------------------------------------------------
    /**
     * The node at the end of the list is removed. Returns the removed node.
     * It also sets the fail state of the node to {@link Fallible#DEAD}.
     */
    public static Node remove() {

        Node n = node[len - 1]; // if len was zero this throws and exception
        node[len - 1] = null;
        len--;
        n.setFailState(Fallible.DEAD);
        return n;
    }

// ------------------------------------------------------------------
    /**
     * The node with the given index is removed. Returns the removed node.
     * It also sets the fail state of the node to {@link Fallible#DEAD}.
     * <p>Look out: the index of the other nodes will not change (the right
     * hand side of the list is not shifted to the left) except that of the last
     * node. Only the
     * last node is moved to the given position and will get index i.
     */
    public static Node remove(int i) {

        if (i < 0 || i >= len) {
            throw new IndexOutOfBoundsException("" + i);
        }
        swap(i, len - 1);
        return remove();
    }

// ------------------------------------------------------------------
    /**
     * Swaps the two nodes at the given indexes.
     */
    public static void swap(int i, int j) {

        Node n = node[i];
        node[i] = node[j];
        node[j] = n;
        node[j].setIndex(j);
        node[i].setIndex(i);
    }

// ------------------------------------------------------------------
    /**
     * Shuffles the node array. The index of each node is updated accordingly.
     */
    public static void shuffle() {

        for (int i = len; i > 1; i--) {
            swap(i - 1, CommonState.r.nextInt(i));
        }
    }

// ------------------------------------------------------------------
    /**
     * Sorts the node array. The index of each node is updated accordingly.
     * @param c The comparator to be used for sorting the nodes. If null, the
     * natural order of the nodes is used.
     */
    public static void sort(Comparator<? super Node> c) {

        Arrays.sort(node, 0, len, c);
        for (int i = 0; i < len; i++) {
            node[i].setIndex(i);
        }
    }

// ------------------------------------------------------------------
    public static void test() {

        System.err.println("number of nodes = " + len);
        System.err.println("capacity (max number of nodes) = " + node.length);
        for (int i = 0; i < len; ++i) {
            System.err.println("node[" + i + "]");
            System.err.println(node[i].toString());
        }

        if (prototype == null) {
            return;
        }
        for (int i = 0; i < prototype.protocolSize(); ++i) {
            if (prototype.getProtocol(i) instanceof Linkable) {
                peersim.graph.GraphIO.writeUCINET_DL(
                        new OverlayGraph(i), System.err);
            }
        }
    }
}
